package be.rivendale.ghetto;

import be.rivendale.geometry.AxisAlignedBoundingBox;
import be.rivendale.geometry.TriangleModel;
import be.rivendale.material.Color;
import be.rivendale.mathematics.Point;
import be.rivendale.mathematics.Triangle;
import be.rivendale.mathematics.Triple;

import javax.swing.*;
import java.util.List;

public class VoxelizerMarkTwo {
	public VoxelModel voxelize(TriangleModel triangleModel, Triple voxelSize, Point minimumBound) {
		// Determine the resolution
		AxisAlignedBoundingBox triangleModelBoundingBox = triangleModel.getAxisAlignedBoundingBox();
		int xResolution = (int)Math.ceil(triangleModelBoundingBox.getWidth() / voxelSize.getX());
		int yResolution = (int)Math.ceil(triangleModelBoundingBox.getHeight() / voxelSize.getY());
		int zResolution = (int)Math.ceil(triangleModelBoundingBox.getDepth() / voxelSize.getZ());

		System.out.format("Original triangle model dimensions: (w: %f, h: %f, d: %f).\n", triangleModelBoundingBox.getWidth(), triangleModelBoundingBox.getHeight(), triangleModelBoundingBox.getDepth());
		System.out.format("Original triangle model minimumBound: (x: %f, y: %f, z: %f).\n", triangleModelBoundingBox.getMinimumBound().getX(), triangleModelBoundingBox.getMinimumBound().getY(), triangleModelBoundingBox.getMinimumBound().getZ());
		System.out.format("Resolution: (x: %d, y: %d, z: %d, t: %d).\n", xResolution, yResolution, zResolution, xResolution * yResolution * zResolution);

		AxisAlignedBoundingBox voxelModelBoundingBox = createVoxelModelBoundingBox(triangleModelBoundingBox.getMinimumBound(), voxelSize, xResolution, yResolution, zResolution);


		// Determine the voxel data
		Voxel[][][] voxels = processEachVoxel(xResolution, yResolution, zResolution, triangleModel.getTriangles(), voxelModelBoundingBox, voxelSize);

		// Wrap and return as voxel model
		return new VoxelModel(minimumBound, voxels, voxelModelBoundingBox.getWidth(), voxelModelBoundingBox.getHeight(), voxelModelBoundingBox.getDepth());
	}

	private AxisAlignedBoundingBox createVoxelModelBoundingBox(Point minimumBound, Triple voxelSize, int xResolution, int yResolution, int zResolution) {
		// Determine the voxel dimensions
		Triple voxelModelDimensions = new Triple(xResolution * voxelSize.getX(),
			yResolution * voxelSize.getY(),
			zResolution * voxelSize.getZ()
		);
		System.out.format("Voxel bounding box: (w: %f, h: %f, d: %f).\n",
				voxelModelDimensions.getX(), voxelModelDimensions.getY(), voxelModelDimensions.getZ());
		return new AxisAlignedBoundingBox(minimumBound, minimumBound.add(voxelModelDimensions));
	}

	private Voxel[][][] processEachVoxel(int xResolution, int yResolution, int zResolution, List<Triangle> triangles, AxisAlignedBoundingBox voxelModelBoundingBox, Triple voxelSize) {
		Voxel[][][] voxels = new Voxel[xResolution][yResolution][zResolution];
        int totalNumberOfVoxels = xResolution * yResolution * zResolution;
        int currentOfTotalVoxels = 0;
        ProgressMonitor monitor = new ProgressMonitor(null, "Voxelizing", null, 0, totalNumberOfVoxels);
        long mark = 0;
        for(int z = 0; z < zResolution; z++) {
            for(int y = 0; y < yResolution; y++) {
                for(int x = 0; x < xResolution; x++) {
                    long current = System.currentTimeMillis();
                    if(current > mark + 250) {
                        String note = String.format("Processing voxel %d of %d (%.2f%%)",
                            currentOfTotalVoxels,
                            totalNumberOfVoxels,
                            (((double)currentOfTotalVoxels / totalNumberOfVoxels)) * 100
                        );
                        monitor.setNote(note);
                        mark = current;
                    }
                    voxels[x][y][z] = processSingleVoxel(x, y, z, triangles, voxelSize, voxelModelBoundingBox);
                    if(monitor.isCanceled()) {
                        System.exit(-1);
                    }
                    monitor.setProgress(currentOfTotalVoxels);
                    currentOfTotalVoxels++;
				}
			}
		}
        monitor.close();
		return voxels;
	}

	private Voxel processSingleVoxel(int x, int y, int z, List<Triangle> triangles, Triple voxelSize, AxisAlignedBoundingBox voxelModelBoundingBox) {
		AxisAlignedBoundingBox voxelBoundingBox = buildVoxelBoundingBox(voxelModelBoundingBox, x, y, z, voxelSize);
		for (Triangle triangle : triangles) {
			if(AabbTriangleIntersectionGhetto.intersects(voxelBoundingBox, triangle)) {
				return new Voxel(assignRandomColor(), triangle.normal().normalize().invert());
			}
		}
		return null;
	}

	private static Color[] availableColors = {Color.RED, Color.GREEN, Color.BLUE, Color.CYAN, Color.MAGENTA, Color.YELLOW};
	private Color assignRandomColor() {
		int colorIndex = (int) (Math.random() * availableColors.length);
		return availableColors[colorIndex];
	}

	private AxisAlignedBoundingBox buildVoxelBoundingBox(AxisAlignedBoundingBox voxelModelBoundingBox, int x, int y, int z, Triple voxelSize) {
		Point minimumBound = voxelModelBoundingBox.getMinimumBound().add(
				new Triple(voxelSize.getX() * x, voxelSize.getY() * y, voxelSize.getZ() * z));
		Point maximumBound = minimumBound.add(voxelSize);

		return new AxisAlignedBoundingBox(minimumBound, maximumBound);
	}
}
